/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
* ViewTree.java
*
* Created on November 28, 2007, 10:01 AM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
 */
package gui.genomemap.tree;

import gui.genomemap.outputModel.OutputModel;
import gui.genomemap.view.View;

import javautil.swing.components.FlexPopupMenu;

import java.awt.Component;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import javax.swing.JComponent;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

/**
 *
 * @author stewari1
 */
public class ViewTree extends JTree {

    /** Field description */
    private DefaultTreeModel defaultTreeModel = null;

    /** Field description */
    private RootNode root = null;

    /** Field description */
    private View view = null;

    /** Field description */
    private final FlexPopupMenu treePopup = new FlexPopupMenu();

    /**
     * actions associated with popup when a single treepath is selected
     */

    /**
     * Creates a new instance of ViewTree
     */
    public ViewTree() {

        getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
        setCellRenderer(new ViewTreeCellRenderer());
        setShowsRootHandles(true);
        setRowHeight(18);


        // add listeners
        addDoubleClickListener();
        addPopupListener();


        // setting up actions for input map and also for popup
        TreeActions treeActions = new TreeActions(this);

        setActionMap(treeActions.getTreeActionMap());
        setInputMap(JComponent.WHEN_FOCUSED, treeActions.getTreeInputMap());
    }

    public void loadData(Collection<? extends OutputModel> outputModels) {


        // sort the output models
        if (outputModels == null) {
            return;
        }

        TreeSet<OutputModel> sortedOutputModels = new TreeSet<>(outputModels);


        // the root
        this.root = new RootNode(getView().getType());


        // create folders for each chromosome
        ChNode[] chNodes = new ChNode[7];

        for (int ch_id_index = 0; ch_id_index < chNodes.length; ch_id_index++) {

            chNodes[ch_id_index] = new ChNode(ch_id_index + 1);

            root.add(chNodes[ch_id_index]);
        }


        // add output models to its ch-folder
        Iterator<OutputModel> itr = sortedOutputModels.iterator();

        while (itr.hasNext()) {

            OutputModel workerOutputModel = itr.next();
            int ch_id_index               = workerOutputModel.getInputModel().getLinkageGroup() - 1;
            ModelTreeNode modelNode       = new ModelTreeNode(workerOutputModel);


            // add model resources to the model node
            if (workerOutputModel.getModelStat().length() > 0) {
                modelNode.add(new ModelStatTreeNode(workerOutputModel.getModelStat()));
            }

            if (!workerOutputModel.getFiguresMap().isEmpty()) {

                Map<Integer, Component> figMap = workerOutputModel.getFiguresMap();
                Iterator<Integer> figKeys      = figMap.keySet().iterator();

                while (figKeys.hasNext()) {

                    Integer figKey = figKeys.next();

                    modelNode.add(new ModelFigTreeNode(figMap.get(figKey), figKey.intValue()));
                }
            }

            if (workerOutputModel.getLog().length() > 0) {
                modelNode.add(new ModelLogTreeNode(workerOutputModel.getLog()));
            }

            chNodes[ch_id_index].add(modelNode);
        }

        root.setOutputModels(outputModels);

        defaultTreeModel = new DefaultTreeModel(root);

        setModel(defaultTreeModel);
    }

    /**
     * Method description
     */
    private void addDoubleClickListener() {

        addMouseListener(new MouseAdapter() {

            public void mousePressed(MouseEvent e) {

                int selRow       = getRowForLocation(e.getX(), e.getY());
                TreePath selPath = getPathForLocation(e.getX(), e.getY());

                if (selRow != -1) {

                    if (e.getClickCount() == 2) {
                        mouseDoubleClicked(selPath);
                    }
                }
            }

        });
    }

    /**
     * Method description
     */
    private void addPopupListener() {

        addMouseListener(new MouseAdapter() {

            public void mouseReleased(MouseEvent e) {

                if (e.isPopupTrigger()) {

                    int x                    = e.getX();
                    int y                    = e.getY();
                    TreePath[] selectedPaths = ViewTree.this.getSelectionPaths();

                    if (selectedPaths == null) {

                        ViewTree.this.setSelectionPath(ViewTree.this.getClosestPathForLocation(x,
                                y));

                        selectedPaths = ViewTree.this.getSelectionPaths();

                        if (selectedPaths == null) {
                            return;
                        }
                    }


                    // if the click is outside, dismiss the popup
                    Rectangle bounds = new Rectangle();

                    for (TreePath treePath : selectedPaths) {

                        bounds = bounds.union(ViewTree.this.getPathBounds(treePath));
                    }

                    if (!bounds.contains(x, y)) {

                        ViewTree.this.removeSelectionPaths(selectedPaths);

                        return;
                    }


                    // fill in the action afresh: remove the previous ones
                    treePopup.removeAll();


                    // common actions (for both SINGLE AND MULTIPLE Selection)
                    treePopup.add(getActionMap().get(TreeActions.KEY_DELETE_MODEL));


                    // Single Selection
                    if (selectedPaths.length == 1) {

                        TreePath selectedPath = selectedPaths[0];


                        // find the node corresponding to the path
                        ViewTreeNode treeNode = (ViewTreeNode) selectedPath.getLastPathComponent();

                        switch (treeNode.getType()) {

                            case ViewTreeNode.TYPE_ROOT :
                                treePopup.add(getActionMap().get(TreeActions.KEY_CREATE_MODEL));

                                break;

                            case ViewTreeNode.TYPE_CHROMOSOME :
                                break;

                            case ViewTreeNode.TYPE_MODEL :
                                treePopup.add(getActionMap().get(TreeActions.KEY_CREATE_MODEL));
                                treePopup.add(getActionMap().get(TreeActions.KEY_EDIT_MODEL));
                                treePopup.add(getActionMap().get(TreeActions.KEY_OUTPUT_OPTIONS));

                                break;

                            case ViewTreeNode.TYPE_MODEL_STAT :
                                treePopup.add(getActionMap().get(TreeActions.KEY_SAVE_AS));

                                break;

                            case ViewTreeNode.TYPE_FIGURES :
                                treePopup.add(getActionMap().get(TreeActions.KEY_SAVE_AS));

                                break;

                            case ViewTreeNode.TYPE_LOG :
                                treePopup.add(getActionMap().get(TreeActions.KEY_SAVE_AS));

                                break;
                        }

                    }    // Multiple Selection
                            else if (selectedPaths.length > 1) {}

                    treePopup.show(ViewTree.this, x, y);
                }
            }

        });
    }


    // @todo ideally should throw mouse doublick click in tree event with the data(output model) to view

    /**
     * Method description
     *
     * @param path description
     */
    private void mouseDoubleClicked(TreePath path) {

        DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();

        if (!(node instanceof ViewTreeNode)) {
            return;
        }

        ViewTreeNode treeNode = (ViewTreeNode) node;

        switch (treeNode.getType()) {

            case ViewTreeNode.TYPE_MODEL :
                ModelTreeNode modelNode = (ModelTreeNode) treeNode;

                getView().displayOutputModel(modelNode);

                break;

            case ViewTreeNode.TYPE_MODEL_STAT :
                ModelStatTreeNode modelStatNode = (ModelStatTreeNode) treeNode;

                getView().displayText(modelStatNode, true);

                break;

            case ViewTreeNode.TYPE_FIGURES :
                ModelFigTreeNode modelFigNode = (ModelFigTreeNode) treeNode;

                getView().displayComponent(modelFigNode);

                break;

            case ViewTreeNode.TYPE_LOG :
                ModelLogTreeNode modelLogNode = (ModelLogTreeNode) treeNode;

                getView().displayText(modelLogNode, false);

                break;
        }
    }

    public void expandModelNode(OutputModel outputModel) {
        expandPath(new TreePath(getModelTreeNode(outputModel).getPath()));
    }

    public void expandChNode(int ch_id) {

        ChNode chNode     = (ChNode) getView().getTree().getRoot().getChildAt(ch_id - 1);
        TreePath treePath = new TreePath(chNode.getPath());

        setSelectionPath(treePath);
        expandPath(treePath);
    }

    public void setModelSelected(OutputModel outputModel) {

        int ch_id       = outputModel.getInputModel().getLinkageGroup();
        ChNode chNode   = (ChNode) root.getChildAt(ch_id - 1);
        Enumeration enr = chNode.breadthFirstEnumeration();

        while (enr.hasMoreElements()) {

            ViewTreeNode node = (ViewTreeNode) enr.nextElement();

            if (node instanceof ModelTreeNode) {

                OutputModel temp = node.getOutputModel();

                if (temp.equals(outputModel)) {

                    TreePath treePath = new TreePath(node.getPath());

                    setSelectionPath(treePath);
                    expandPath(treePath);
                    mouseDoubleClicked(treePath);
                }
            }
        }
    }

    ModelTreeNode getModelTreeNode(OutputModel outputModel) {

        int ch_id       = outputModel.getInputModel().getLinkageGroup();
        ChNode chNode   = (ChNode) root.getChildAt(ch_id - 1);
        Enumeration enr = chNode.breadthFirstEnumeration();

        while (enr.hasMoreElements()) {

            ViewTreeNode node = (ViewTreeNode) enr.nextElement();

            if (node instanceof ModelTreeNode) {

                OutputModel temp = node.getOutputModel();

                if (temp.equals(outputModel)) {
                    return (ModelTreeNode) node;
                }
            }
        }

        return null;
    }

    public Set<OutputModel> getOutputModels(TreePath[] treePaths) {

        Set<OutputModel> models = new HashSet<>();

        for (TreePath path : treePaths) {

            models.add(getOutputModelForPath(path));
        }

        return models;
    }

    OutputModel getOutputModelForPath(TreePath path) {

        DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();

        if (node instanceof ModelTreeNode) {
            return ((ModelTreeNode) node).getOutputModel();
        }

        return null;
    }

    public boolean isRoot(TreePath treePath) {

        DefaultMutableTreeNode node = (DefaultMutableTreeNode) treePath.getLastPathComponent();

        return node.isRoot();
    }

    public View getView() {
        return view;
    }

    public void setView(View view) {
        this.view = view;
    }

    RootNode getRoot() {
        return root;
    }

    public void setRoot(RootNode root) {
        this.root = root;
    }
}
